<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>测试监控Dashboard - Aomaker</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="icon" href="https://picgo2listen.oss-cn-beijing.aliyuncs.com/imgs/aomaker_light.png" type="image/png">
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        'aomaker-purple': { light: '#8B5CF6', DEFAULT: '#6366F1', dark: '#4F46E5' },
                        'status-passed': '#5CBA95',
                        'status-failed': '#E88A8A',
                        'status-broken': '#E6B55C',
                        'status-skipped': '#7A9FE2',
                        'bg-gradient-start': '#F0F3FF',
                        'bg-gradient-end': '#FAFAFD',
                        'legend-bg': 'rgba(255, 255, 255, 0.2)',
                        'legend-border': '#EAEAF5',
                        primary: {
                            50: '#f5f3ff',
                            100: '#ede9fe',
                            200: '#ddd6fe',
                            300: '#c4b5fd',
                            400: '#a78bfa',
                            500: '#8b5cf6',
                            600: '#7c3aed',
                            700: '#6d28d9',
                            800: '#5b21b6',
                            900: '#4c1d95',
                            950: '#2e1065',
                        },
                        success: '#10b981',
                        warning: '#f59e0b',
                        error: '#ef4444',
                        info: '#3b82f6',
                    },
                    fontFamily: {
                        sans: ['Inter', 'sans-serif'],
                        mono: ['SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'monospace'],
                    },
                    keyframes: {
                        fadeHighlight: {
                          'from': { backgroundColor: 'rgba(139, 92, 246, 0.2)' },
                          'to': { backgroundColor: 'transparent' },
                        },
                        fadeIn: {
                          'from': { opacity: '0' },
                          'to': { opacity: '1' },
                        },
                        gradientAnimation: {
                            '0%': { backgroundPosition: '0% 0%, 100% 100%, 0% 50%' },
                            '25%': { backgroundPosition: '2% 5%, 95% 97%, 30% 60%' },
                            '50%': { backgroundPosition: '0% 15%, 85% 100%, 60% 70%' },
                            '75%': { backgroundPosition: '5% 2%, 97% 95%, 70% 40%' },
                            '100%': { backgroundPosition: '0% 0%, 100% 100%, 0% 50%' },
                        },
                        glowPulse: {
                            '0%, 100%': { opacity: '0.3' },
                            '50%': { opacity: '0.7' },
                        }
                    },
                    animation: {
                        'fade-highlight': 'fadeHighlight 1s ease',
                        'fade-in': 'fadeIn 1s ease',
                        'gradient': 'gradientAnimation 25s ease-in-out infinite alternate',
                        'glow-pulse': 'glowPulse 2s ease-in-out infinite',
                    },
                    borderRadius: {
                        'xl': '1rem',
                        'lg': '0.75rem',
                    },
                    boxShadow: {
                        'neu-outer': '4px 4px 8px rgba(0, 0, 0, 0.04), -4px -4px 8px rgba(255, 255, 255, 0.6)',
                        'neu-inner': 'inset 2px 2px 4px rgba(0, 0, 0, 0.03), inset -2px -2px 4px rgba(255, 255, 255, 0.7)',
                        'neu-card': 'var(--shadow-neu-outer), var(--shadow-neu-inner)',
                        'neu-pill-active': 'inset 2px 2px 4px rgba(0, 0, 0, 0.1)',
                        'pill-hover': '0 1px 4px rgba(0, 0, 0, 0.06)',
                    },
                },
            },
        }
    </script>
    <style type="text/tailwindcss">
        :root {
            --color-success: #5CBA95;
            --color-error: #E88A8A;
            --color-warning: #E6B55C;
            --color-info: #7A9FE2;
            --shadow-neu-outer: 0 10px 15px -3px rgba(0, 0, 0, 0.04), 0 4px 6px -2px rgba(0, 0, 0, 0.02);
            --shadow-neu-inner: inset 0 1px 2px rgba(255, 255, 255, 0.1);
            --purple-glow-1: rgba(139, 92, 246, 0.2);
            --purple-glow-2: rgba(99, 102, 241, 0.2);
            --purple-fade-1: rgba(139, 92, 246, 0.02);
            --purple-fade-2: rgba(99, 102, 241, 0.02);
        }
        
        /* 基础样式 */
        body {
            background: 
                radial-gradient(circle at 5% 5%, var(--purple-glow-1) 0%, rgba(139, 92, 246, 0.08) 15%, var(--purple-fade-1) 40%, transparent 70%),
                radial-gradient(circle at 95% 95%, var(--purple-glow-2) 0%, rgba(99, 102, 241, 0.08) 15%, var(--purple-fade-2) 40%, transparent 70%),
                linear-gradient(to bottom right, var(--color-from, #F0F3FF), var(--color-to, #FAFAFD));
            @apply min-h-screen font-sans text-sm text-gray-600 antialiased;
            background-attachment: fixed;
            background-size: 200% 200%, 200% 200%, 400% 400%;
            --color-from: #F0F3FF;
            --color-to: #FAFAFD;
            animation: gradientAnimation 25s ease-in-out infinite alternate;
        }
        
        @keyframes gradientAnimation {
            0% { background-position: 0% 0%, 100% 100%, 0% 50%; }
            25% { background-position: 2% 5%, 95% 97%, 30% 60%; }
            50% { background-position: 0% 15%, 85% 100%, 60% 70%; }
            75% { background-position: 5% 2%, 97% 95%, 70% 40%; }
            100% { background-position: 0% 0%, 100% 100%, 0% 50%; }
        }
        
        .neu-card {
            @apply bg-white/20 rounded-xl p-6 border border-white/30 backdrop-blur-md;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(255, 255, 255, 0.15) inset;
            transition: all 0.3s ease-out, transform 0.2s ease-out;
            position: relative;
            z-index: 1;
        }
        
        .neu-card:hover {
            @apply bg-white/30 border-white/40;
            box-shadow: 0 12px 36px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.25) inset, 0 0 20px rgba(99, 102, 241, 0.15);
            transform: translateY(-5px) scale(1.01);
            z-index: 2;
        }
        
        .neu-card:hover::after {
            content: '';
            position: absolute;
            inset: -2px;
            background: linear-gradient(225deg, rgba(139, 92, 246, 0.2), rgba(99, 102, 241, 0.2), transparent 60%);
            border-radius: inherit;
            z-index: -1;
            opacity: 0;
            animation: glow-pulse 2s ease-in-out infinite;
        }
        
        @keyframes glow-pulse {
            0%, 100% { opacity: 0.3; }
            50% { opacity: 0.7; }
        }
        
        .custom-scrollbar {
            scrollbar-width: thin; /* Firefox */
            scrollbar-color: rgba(99, 102, 241, 0.2) transparent; /* Firefox */
        }
        
        .custom-scrollbar::-webkit-scrollbar {
            width: 6px;
            height: 6px;
        }
        
        .custom-scrollbar::-webkit-scrollbar-track {
            background: transparent;
        }
        
        .custom-scrollbar::-webkit-scrollbar-thumb {
            background-color: rgba(99, 102, 241, 0.2);
            border-radius: 6px;
        }
        
        .custom-scrollbar::-webkit-scrollbar-thumb:hover {
            background-color: rgba(99, 102, 241, 0.4);
        }
    </style>
</head>
<body class="min-h-screen transition-colors duration-300">
    <div class="min-h-screen flex flex-col">
        <!-- 导航栏 -->
        <nav class="bg-white/20 backdrop-blur-md shadow-sm sticky top-0 z-10 border-b border-white/20">
            <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
                <div class="flex justify-between h-16">
                    <div class="flex items-center">
                        <img src="https://picgo2listen.oss-cn-beijing.aliyuncs.com/imgs/aomaker-logo.png" alt="Aomaker Logo" class="h-10 w-auto transform hover:scale-105 transition-transform">
                        <span class="ml-3 text-xl font-semibold text-gray-900 dark:text-white"></span>
                    </div>
                    <div class="flex items-center space-x-4">
                        <div class="flex items-center space-x-2">
                            <span id="testStatusBadge" class="hidden px-2 py-1 text-xs font-medium rounded-full bg-white/30 text-gray-800 backdrop-blur-sm">
                                空闲
                            </span>
                            <button id="darkModeToggle" class="hidden p-1 rounded-full text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 focus:outline-none">
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
                                </svg>
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        </nav>

        <!-- 主要内容 -->
        <main class="flex-grow">
            <!-- 仪表盘概览 -->
            <div class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
                <div class="px-4 py-4 sm:px-0">
                    <div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
                        <!-- 仪表盘卡片 - 测试总数 -->
                        <div class="neu-card" style="animation-delay: 50ms">
                            <div class="flex items-center">
                                <div class="flex-shrink-0 bg-primary-100/50 backdrop-blur-sm rounded-md p-3">
                                    <svg class="h-6 w-6 text-primary-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
                                    </svg>
                                </div>
                                <div class="ml-5 w-0 flex-1">
                                    <dl>
                                        <dt class="text-sm font-medium text-gray-500 truncate">
                                            测试总数
                                        </dt>
                                        <dd>
                                            <div class="text-lg font-medium text-gray-900" id="totalTestsCount">-</div>
                                        </dd>
                                    </dl>
                                </div>
                            </div>
                        </div>

                        <!-- 仪表盘卡片 - 活跃工作进程 -->
                        <div class="neu-card" style="animation-delay: 100ms">
                            <div class="flex items-center">
                                <div class="flex-shrink-0 bg-green-100/50 backdrop-blur-sm rounded-md p-3">
                                    <svg class="h-6 w-6 text-green-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
                                    </svg>
                                </div>
                                <div class="ml-5 w-0 flex-1">
                                    <dl>
                                        <dt class="text-sm font-medium text-gray-500 truncate">
                                            活跃工作进程
                                        </dt>
                                        <dd>
                                            <div class="text-lg font-medium text-gray-900" id="activeWorkersCount">-</div>
                                        </dd>
                                    </dl>
                                </div>
                            </div>
                        </div>

                        <!-- 仪表盘卡片 - 已完成测试 -->
                        <div class="neu-card" style="animation-delay: 150ms">
                            <div class="flex items-center">
                                <div class="flex-shrink-0 bg-blue-100/50 backdrop-blur-sm rounded-md p-3">
                                    <svg class="h-6 w-6 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
                                    </svg>
                                </div>
                                <div class="ml-5 w-0 flex-1">
                                    <dl>
                                        <dt class="text-sm font-medium text-gray-500 truncate">
                                            已完成测试
                                        </dt>
                                        <dd>
                                            <div class="text-lg font-medium text-gray-900" id="completedTestsCount">-</div>
                                        </dd>
                                    </dl>
                                </div>
                            </div>
                        </div>

                        <!-- 仪表盘卡片 - 整体进度 -->
                        <div class="neu-card" style="animation-delay: 200ms">
                            <div class="flex items-center">
                                <div class="flex-shrink-0 bg-indigo-100/50 backdrop-blur-sm rounded-md p-3">
                                    <svg class="h-6 w-6 text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
                                    </svg>
                                </div>
                                <div class="ml-5 w-0 flex-1">
                                    <dl>
                                        <dt class="text-sm font-medium text-gray-500 truncate">
                                            整体进度
                                        </dt>
                                        <dd>
                                            <div class="text-lg font-medium text-gray-900" id="overallProgressPercent">-</div>
                                        </dd>
                                    </dl>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Main Content Sections -->
                <div class="mt-6 px-4 sm:px-0">
                    <div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
                        <!-- Test Progress Section -->
                        <div class="neu-card p-0 overflow-hidden" style="animation-delay: 250ms">
                            <div class="px-4 py-5 sm:px-6 flex justify-between items-center border-b border-white/20">
                                <div class="flex items-center">
                                    <i class="fas fa-tasks text-primary-600 mr-2"></i>
                                    <h3 class="text-lg leading-6 font-medium text-gray-900">测试进度</h3>
                                </div>
                            </div>
                            <div>
                                <div class="px-4 py-5 sm:p-6 max-h-[400px] overflow-y-auto custom-scrollbar" id="progressContainer">
                                    <div id="progressEmptyState" class="flex flex-col items-center justify-center py-8">
                                        <div class="text-primary-500 mb-4">
                                            <svg class="h-12 w-12 animate-pulse" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
                                            </svg>
                                        </div>
                                        <p class="text-gray-500 text-sm">等待测试开始...</p>
                                    </div>
                                </div>
                                <div class="px-4 py-4 sm:px-6 border-t border-white/20 flex justify-between items-center">
                                    <span class="text-sm text-gray-500">最后更新: <span id="lastUpdatedTime">-</span></span>
                                    <button id="viewReportButton" class="hidden inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 transition-all duration-200 transform hover:scale-105">
                                        <i class="fas fa-file-alt mr-2"></i> 查看报告
                                    </button>
                                </div>
                            </div>
                        </div>

                        <!-- Configuration Section -->
                        <div class="neu-card p-0 overflow-hidden" style="animation-delay: 300ms">
                            <div class="px-4 py-5 sm:px-6 flex justify-between items-center border-b border-white/20">
                                <div class="flex items-center">
                                    <i class="fas fa-cog text-primary-600 mr-2"></i>
                                    <h3 class="text-lg leading-6 font-medium text-gray-900">配置信息</h3>
                                </div>
                                <button id="expandConfigButton" class="inline-flex items-center px-3 py-1 border border-transparent text-sm font-medium rounded-md text-primary-700 bg-primary-100/50 backdrop-blur-sm hover:bg-primary-200/60 focus:outline-none">
                                    <i class="fas fa-expand-alt"></i>
                                </button>
                            </div>
                            <div>
                                <div class="px-4 py-5 sm:p-6 max-h-[400px] overflow-y-auto custom-scrollbar">
                                    <div id="configEmptyState" class="flex flex-col items-center justify-center py-8">
                                        <div class="text-primary-500 mb-4">
                                            <svg class="h-12 w-12 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
                                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
                                            </svg>
                                        </div>
                                        <p class="text-gray-500 text-sm">加载配置信息...</p>
                                    </div>
                                    <div class="hidden" id="configContent">
                                        <div class="rounded-md overflow-hidden border border-white/30">
                                            <table class="min-w-full divide-y divide-white/20" id="configTable">
                                                <thead class="bg-white/10 backdrop-blur-sm">
                                                    <tr>
                                                        <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">参数</th>
                                                        <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">值</th>
                                                    </tr>
                                                </thead>
                                                <tbody class="bg-white/10 backdrop-blur-sm divide-y divide-white/20" id="configTableBody">
                                                    <!-- 配置数据将在这里插入 -->
                                                </tbody>
                                            </table>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Logs Section -->
                <div class="mt-6 px-4 sm:px-0">
                    <div class="neu-card p-0 overflow-hidden" style="animation-delay: 350ms">
                        <div class="px-4 py-5 sm:px-6 flex justify-between items-center border-b border-white/20">
                            <div class="flex items-center">
                                <i class="fas fa-terminal text-primary-600 mr-2"></i>
                                <h3 class="text-lg leading-6 font-medium text-gray-900">实时日志</h3>
                            </div>
                            <div class="flex items-center space-x-2">
                                <div class="relative flex-grow">
                                    <input id="logSearchInput" type="text" class="block w-full pl-3 pr-10 py-2 border-gray-300 rounded-md shadow-sm bg-white/50 backdrop-blur-sm focus:ring-primary-500 focus:border-primary-500 sm:text-sm" placeholder="搜索日志...">
                                    <div class="absolute top-1/2 right-3 transform -translate-y-1/2 pointer-events-none">
                                        <svg class="w-4 h-4 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
                                        </svg>
                                    </div>
                                </div>
                                <div class="relative">
                                    <button id="logFilterButton" class="inline-flex items-center px-3 py-2 border border-gray-300 text-sm leading-4 font-medium rounded-md text-gray-700 bg-white/50 hover:bg-white/70 focus:outline-none focus:ring-1 focus:ring-primary-500 focus:border-primary-500 backdrop-blur-sm">
                                        <i class="fas fa-filter mr-1"></i> 全部 
                                        <svg class="-mr-1 ml-1 h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
                                            <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
                                        </svg>
                                    </button>
                                    <div id="logFilterDropdown" class="origin-top-right absolute right-0 mt-2 w-40 rounded-md shadow-lg bg-white/90 backdrop-blur-sm ring-1 ring-black ring-opacity-5 focus:outline-none z-10 hidden">
                                        <div class="py-1" role="menu" aria-orientation="vertical">
                                            <a href="#" class="log-filter-option block px-4 py-2 text-sm text-gray-700 hover:bg-primary-100/50 hover:text-primary-900" data-filter="all" role="menuitem">全部</a>
                                            <a href="#" class="log-filter-option block px-4 py-2 text-sm text-gray-700 hover:bg-primary-100/50 hover:text-primary-900" data-filter="info" role="menuitem">信息</a>
                                            <a href="#" class="log-filter-option block px-4 py-2 text-sm text-gray-700 hover:bg-primary-100/50 hover:text-primary-900" data-filter="warning" role="menuitem">警告</a>
                                            <a href="#" class="log-filter-option block px-4 py-2 text-sm text-gray-700 hover:bg-primary-100/50 hover:text-primary-900" data-filter="error" role="menuitem">错误</a>
                                        </div>
                                    </div>
                                </div>
                                <button id="downloadLogsButton" class="inline-flex items-center px-3 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white/50 hover:bg-white/70 focus:outline-none focus:ring-1 focus:ring-primary-500 focus:border-primary-500 backdrop-blur-sm">
                                    <i class="fas fa-download mr-1"></i> 下载
                                </button>
                                <button id="clearLogsButton" class="inline-flex items-center px-3 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-primary-600 hover:bg-primary-700 focus:outline-none">
                                    <i class="fas fa-eraser mr-1"></i> 清除
                                </button>
                            </div>
                        </div>
                        <div>
                            <div id="logContainer" class="p-4 h-[400px] overflow-y-auto bg-gray-50/30 backdrop-blur-sm font-mono text-sm text-gray-800 custom-scrollbar">
                                <div id="logEmptyState" class="flex flex-col items-center justify-center h-full">
                                    <div class="text-primary-500 mb-4">
                                        <svg class="h-12 w-12" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
                                        </svg>
                                    </div>
                                    <p class="text-gray-500">等待日志输出...</p>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </main>

        <!-- 页脚 -->
        <footer class="bg-white/20 backdrop-blur-sm shadow-inner mt-10 border-t border-white/30">
            <div class="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
                <div class="flex justify-between items-center">
                    <p class="text-sm text-gray-600">© 2023 Aomaker. All rights reserved.</p>
                    <p class="text-sm text-gray-600">服务器时间: <span id="serverTime"></span></p>
                </div>
            </div>
        </footer>
    </div>

    <!-- 配置模态框 -->
    <div id="configModal" class="fixed inset-0 z-50 hidden">
        <div class="absolute inset-0 bg-gray-500/40 backdrop-blur-sm transition-opacity"></div>
        <div class="bg-white/80 backdrop-blur-md rounded-xl overflow-hidden shadow-xl transform transition-all max-w-5xl mx-auto my-16 max-h-[80vh] flex flex-col border border-white/30">
            <div class="px-4 py-5 sm:px-6 flex justify-between items-center border-b border-white/30">
                <h3 class="text-lg leading-6 font-medium text-gray-900">配置详情</h3>
                <button id="closeConfigModal" class="text-gray-400 hover:text-gray-500 focus:outline-none">
                    <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
                    </svg>
                </button>
            </div>
            <div class="border-t border-white/20 flex-grow overflow-auto custom-scrollbar">
                <div class="px-4 py-5 sm:p-6">
                    <div class="rounded-md overflow-hidden border border-white/30">
                        <table class="min-w-full divide-y divide-white/20" id="configTableModal">
                            <thead class="bg-white/10 backdrop-blur-sm">
                                <tr>
                                    <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">参数</th>
                                    <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">值</th>
                                </tr>
                            </thead>
                            <tbody class="bg-white/10 backdrop-blur-sm divide-y divide-white/20" id="configTableBodyModal">
                                <!-- 配置数据将在这里插入 -->
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- 通知 -->
    <div id="notification" class="fixed right-5 bottom-5 max-w-sm w-full bg-white/80 backdrop-blur-md rounded-xl shadow-lg pointer-events-auto border border-white/30 hidden transform transition-all duration-300 scale-95 opacity-0">
        <div class="p-4">
            <div class="flex items-start">
                <div class="flex-shrink-0" id="notificationIcon">
                    <!-- 图标将动态插入 -->
                </div>
                <div class="ml-3 w-0 flex-1 pt-0.5">
                    <p class="text-sm font-medium text-gray-900" id="notificationTitle">通知标题</p>
                    <p class="mt-1 text-sm text-gray-500" id="notificationMessage">通知消息内容。</p>
                </div>
                <div class="ml-4 flex-shrink-0 flex">
                    <button id="closeNotification" class="bg-transparent rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none">
                        <span class="sr-only">关闭</span>
                        <svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
                            <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
                        </svg>
                    </button>
                </div>
            </div>
        </div>
    </div>

    <script>
        let log_ws;
        let isLogWebSocketInitialized = false;
        const port = window.location.port || (window.location.protocol === 'https:' ? '443' : '80');

        // Log message queue processing
        let logQueue = [];
        let isProcessingLogs = false;
        let currentFilter = 'all';
        let currentLogSearch = '';

        // Store worker data
        let workersData = {};

        // Update server time
        function updateServerTime() {
            const now = new Date();
            document.getElementById('serverTime').textContent = now.toLocaleString();
        }
        setInterval(updateServerTime, 1000);
        updateServerTime();

        // Initialize dark mode
        function initDarkMode() {
            if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
                document.documentElement.classList.add('dark');
            } else {
                document.documentElement.classList.remove('dark');
            }

            document.getElementById('darkModeToggle').addEventListener('click', () => {
                if (document.documentElement.classList.contains('dark')) {
                    document.documentElement.classList.remove('dark');
                } else {
                    document.documentElement.classList.add('dark');
                }
            });
        }
        initDarkMode();

        // Log message queue processing
        function processLogQueue() {
            if (isProcessingLogs || logQueue.length === 0) return;

            isProcessingLogs = true;
            const logContainer = document.getElementById('logContainer');
            const atBottom = logContainer.scrollTop + logContainer.clientHeight >= logContainer.scrollHeight - 50;

            while (logQueue.length > 0) {
                const message = logQueue.shift();
                // 移除空状态
                const logEmptyState = document.getElementById('logEmptyState');
                if (logEmptyState) {
                    logEmptyState.remove();
                }

                const logEntry = document.createElement('div');
                logEntry.textContent = message;
                logEntry.className = 'log-entry py-1 border-b border-white/10 whitespace-pre-wrap break-all transition-colors duration-300 animate-fade-highlight';

                // 根据日志类型添加颜色
                if (message.toLowerCase().includes('error')) {
                    logEntry.classList.add('text-status-failed');
                } else if (message.toLowerCase().includes('warning')) {
                    logEntry.classList.add('text-status-broken');
                } else if (message.toLowerCase().includes('info')) {
                    logEntry.classList.add('text-status-skipped');
                }

                logContainer.appendChild(logEntry);

                // 限制最大日志条数，防止内存占用过高
                if (logContainer.children.length > 500) {
                    logContainer.removeChild(logContainer.firstChild);
                }

                // 没有需要移除的高亮效果了，因为我们使用了Tailwind的animate-fade-highlight
            }

            // 更可靠的滚动方法
            if (atBottom) {
                setTimeout(() => {
                    logContainer.scrollTop = logContainer.scrollHeight;
                }, 10);
            }

            isProcessingLogs = false;
        }

        function initializeLogWebSocket() {
            if (!log_ws || log_ws.readyState !== WebSocket.OPEN) {
                log_ws = new WebSocket(`ws://localhost:${port}/ws/logs`);
                log_ws.onclose = () => {
                    console.log("Log WebSocket connection closed");
                    // 检查是否应该重新连接
                    setTimeout(() => {
                        if (isLogWebSocketInitialized) {
                            console.log("重新连接日志WebSocket");
                            initializeLogWebSocket();
                        }
                    }, 1000);
                };

                log_ws.onmessage = function(event) {
                    // 确保不会在日志活动时错误地显示空状态
                    const logEmptyState = document.getElementById('logEmptyState');
                    if (logEmptyState) {
                        logEmptyState.remove();
                    }

                    logQueue.push(event.data);
                    requestAnimationFrame(processLogQueue);
                };

                log_ws.timeout = 2000;
            }
        }

        // 定期处理日志队列，确保日志不会堆积
        setInterval(processLogQueue, 100);

        // 显示配置的空状态
        document.getElementById('configEmptyState').style.display = 'flex';

        // 处理配置数据
        function getConfig() {
            fetch(`http://localhost:${port}/config/current`)
                .then(response => response.json())
                .then(data => {
                    // Create a clean, modern table with proper styling
                    const configTableBody = document.getElementById('configTableBody');
                    const configTableModal = document.getElementById('configTableBodyModal');

                    // Clear existing content
                    configTableBody.innerHTML = '';
                    configTableModal.innerHTML = '';

                    // Style the tables first
                    ['configTable', 'configTableModal'].forEach(tableId => {
                        const table = document.getElementById(tableId);
                        table.className = 'min-w-full divide-y divide-white/20';

                        // Remove existing headers
                        const headerRow = table.querySelector('thead tr');
                        headerRow.innerHTML = '';

                        // Create new header cells
                        const paramHeader = document.createElement('th');
                        paramHeader.className = 'text-left py-3 px-4 font-medium text-xs uppercase tracking-wider text-gray-500';
                        paramHeader.textContent = '参数';
                        paramHeader.style.width = '30%'; // Fixed width for parameter column

                        const valueHeader = document.createElement('th');
                        valueHeader.className = 'text-left py-3 px-4 font-medium text-xs uppercase tracking-wider text-gray-500';
                        valueHeader.textContent = '值';

                        headerRow.appendChild(paramHeader);
                        headerRow.appendChild(valueHeader);
                    });

                    // Add data rows with the new styling
                    for (const [key, value] of Object.entries(data)) {
                        let formattedValue = value;
                        let isJson = false;

                        // Format objects and JSON for better display
                        if (typeof value === 'object' && value !== null) {
                            try {
                                // Format JSON with proper indentation for readability
                                formattedValue = JSON.stringify(value, null, 2);
                                isJson = true;
                            } catch (e) {
                                formattedValue = String(value);
                            }
                        }

                        // Create rows for both tables
                        [configTableBody, configTableModal].forEach(tableBody => {
                            const row = document.createElement('tr');
                            row.className = 'hover:bg-white/20 hover:backdrop-blur-md transition-colors duration-150';

                            const keyCell = document.createElement('td');
                            keyCell.className = 'py-3 px-4 text-primary-500 font-medium border-b border-white/20';
                            keyCell.textContent = key;

                            const valueCell = document.createElement('td');
                            valueCell.className = 'py-3 px-4 text-gray-800 border-b border-white/20';

                            // Handle different value types
                            if (isJson) {
                                // Create a pre element for formatted JSON
                                const pre = document.createElement('pre');
                                pre.className = 'text-xs bg-white/10 backdrop-blur-sm p-3 rounded-md overflow-x-auto whitespace-pre-wrap break-all border border-white/20 text-gray-700';
                                pre.style.maxHeight = '200px'; // Limit height for very long values
                                pre.textContent = formattedValue;
                                valueCell.appendChild(pre);
                            } else if (String(formattedValue).length > 100) {
                                // For long text values, add truncation with expand option
                                const truncatedDiv = document.createElement('div');
                                truncatedDiv.className = 'truncate max-w-md';
                                truncatedDiv.textContent = formattedValue;

                                const expandBtn = document.createElement('button');
                                expandBtn.className = 'text-xs text-primary-500 hover:text-primary-600 ml-1 mt-1';
                                expandBtn.textContent = '显示全部';

                                expandBtn.addEventListener('click', () => {
                                    const fullTextDiv = document.createElement('div');
                                    fullTextDiv.className = 'whitespace-pre-wrap break-all text-xs bg-white/10 backdrop-blur-sm p-3 rounded-md mt-1 border border-white/20 text-gray-700';
                                    fullTextDiv.textContent = formattedValue;

                                    if (truncatedDiv.parentNode) {
                                        truncatedDiv.parentNode.replaceChild(fullTextDiv, truncatedDiv);
                                        expandBtn.textContent = '收起';

                                        expandBtn.addEventListener('click', () => {
                                            if (fullTextDiv.parentNode) {
                                                fullTextDiv.parentNode.replaceChild(truncatedDiv, fullTextDiv);
                                                expandBtn.textContent = '显示全部';
                                            }
                                        }, { once: true });
                                    }
                                });

                                valueCell.appendChild(truncatedDiv);
                                valueCell.appendChild(expandBtn);
                            } else {
                                // For normal-sized values
                                valueCell.textContent = formattedValue;
                            }

                            row.appendChild(keyCell);
                            row.appendChild(valueCell);
                            tableBody.appendChild(row);
                        });
                    }

                    // Hide the empty state
                    document.getElementById('configEmptyState').style.display = 'none';

                    // Show the config content
                    document.getElementById('configContent').classList.remove('hidden');
                })
                .catch(error => {
                    console.error('Error:', error);
                    document.getElementById('configEmptyState').querySelector('p').textContent = '加载配置信息失败';
                });
        }

        function checkAllProgressCompleted(progressData) {
            const allCompleted = Object.values(progressData).every(progress => {
                return progress.completed === progress.total;
            });
            if (allCompleted) {
                document.getElementById('viewReportButton').style.display = 'inline-flex';
                // 添加一个淡入效果
                const reportButton = document.getElementById('viewReportButton');
                reportButton.style.animation = 'fadeIn 0.5s ease-out';
            }
        }

        // 添加一个全局对象来存储每个worker的target值
        const workerTargets = {};

        function updateProgressBars(progressData) {
            Object.entries(progressData).forEach(([workerName, progressInfo], index) => {
                const progressBarFill = document.querySelector(`.h-full[data-worker='${workerName}']`);
                const progressText = document.querySelector(`.flex.justify-between.text-xs[data-worker='${workerName}']`);
                const progressContainer = document.querySelector(`.mb-4.p-3.rounded-xl[data-worker='${workerName}']`);
                const workerIcon = document.querySelector(`i[data-worker='${workerName}']`);
                const badgeElement = document.querySelector(`span[data-worker='${workerName}']`);

                // 只有当target有值时才存储，避免空值覆盖已有值
                if (progressInfo.target && progressInfo.target.trim() !== '') {
                    workerTargets[workerName] = progressInfo.target;
                }

                if (progressBarFill && progressText) {
                    const width = (progressInfo.completed / progressInfo.total) * 100 || 0; // 防止NaN
                    progressBarFill.style.width = width + '%';
                    progressText.innerHTML = `<span>${Math.round(width)}%</span> <span>${progressInfo.completed}/${progressInfo.total}</span>`;

                    // 更新badge内容，始终优先使用已存储的target值
                    if (badgeElement) {
                        // 获取最佳的target值
                        let targetToShow = '';
                        if (workerTargets[workerName]) {
                            targetToShow = workerTargets[workerName]; // 首选：全局存储的target
                        } else if (progressInfo.target && progressInfo.target.trim() !== '') {
                            targetToShow = progressInfo.target; // 次选：当前数据中的target
                        } else if (badgeElement.textContent && badgeElement.textContent.trim() !== '' && 
                                 badgeElement.textContent !== '未知') {
                            targetToShow = badgeElement.textContent; // 第三选择：当前元素中非空、非"未知"内容
                        }
                        
                        // 如果是第一个worker且没有target信息，添加一个默认标签
                        if (targetToShow === '' && Object.keys(previousData).length > 0 && 
                            Object.keys(previousData)[0] === workerName) {
                            targetToShow = '主进程';
                            workerTargets[workerName] = targetToShow; // 保存以便后续使用
                        }
                        
                        // 只在有实际内容时更新标签内容
                        if (targetToShow) {
                            badgeElement.textContent = targetToShow;
                            // 确保标签可见
                            badgeElement.classList.remove('hidden');
                        } else {
                            // 如果没有内容，考虑隐藏标签
                            badgeElement.classList.add('hidden');
                        }
                    }

                    if (progressInfo.completed === progressInfo.total) {
                        // 更新进度条填充样式
                        progressBarFill.classList.remove('bg-primary-500');
                        progressBarFill.classList.add('bg-status-passed');

                        // 更新容器背景色 - 保持柔和的风格
                        progressContainer.classList.remove('bg-white/10');
                        progressContainer.classList.add('bg-white/15');

                        // 修改图标为完成图标
                        if (workerIcon) {
                            workerIcon.classList.remove('fa-cog', 'fa-spin');
                            workerIcon.classList.add('fa-check-circle');
                            workerIcon.classList.remove('text-primary-500');
                            workerIcon.classList.add('text-status-passed');
                        }
                    } else {
                        // 确保进行中的任务保持齿轮旋转效果
                        if (workerIcon) {
                            workerIcon.classList.remove('fa-check-circle');
                            workerIcon.classList.add('fa-cog', 'fa-spin');
                            workerIcon.classList.remove('text-status-passed');
                            workerIcon.classList.add('text-primary-500');
                        }
                    }
                }
            });
        }

        let previousData = {};
        const progress_ws = new WebSocket(`ws://localhost:${port}/ws/progress`);

        // 添加打开和错误处理程序
        progress_ws.onopen = function() {
            console.log("Progress WebSocket连接已打开");
            showNotification("连接成功", "已连接到数据服务器", "success");
        };

        progress_ws.onerror = function(error) {
            console.error("Progress WebSocket错误:", error);
            showNotification("连接错误", "无法连接到数据服务器，请检查服务是否运行", "error");
        };

        progress_ws.onclose = function() {
            console.log("Progress WebSocket连接已关闭");

            // 尝试重新连接
            setTimeout(function() {
                console.log("尝试重新连接...");
                // 重新创建WebSocket连接
                const new_ws = new WebSocket(`ws://localhost:${port}/ws/progress`);
                // 复制原来的处理程序
                new_ws.onmessage = progress_ws.onmessage;
                new_ws.onopen = progress_ws.onopen;
                new_ws.onerror = progress_ws.onerror;
                new_ws.onclose = progress_ws.onclose;

                // 更新全局引用
                progress_ws = new_ws;
            }, 3000);
        };

        // 创建一个记录最后一次测试数据的变量
        let lastTotalData = null;

        // 添加一个变量来跟踪数据是否真正变化
        let lastProgressSnapshot = JSON.stringify({});

        // 修改进度数据处理函数，添加数据记录
        progress_ws.onmessage = function (event) {
            let data = JSON.parse(event.data);
            console.log("接收到进度数据:", data);

            // 修复累加问题 - 检测并重置Total值
            if (data['Total']) {
                // 如果是第一次收到数据，或者Total比上次小，则使用当前值
                if (!lastTotalData ||
                    data['Total'].total < lastTotalData.total ||
                    data['Total'].completed < lastTotalData.completed) {
                    lastTotalData = {...data['Total']};
                } else {
                    // 如果Total比上次大，则手动计算当前总数
                    let currentTotalTests = 0;
                    let currentCompletedTests = 0;

                    // 手动累加所有worker的测试用例数
                    Object.entries(data).forEach(([name, info]) => {
                        if (name !== 'Total') {
                            currentTotalTests += info.total;
                            currentCompletedTests += info.completed;
                        }
                    });

                    // 用计算得出的值覆盖服务器给出的值
                    data['Total'] = {
                        target: "",
                        completed: currentCompletedTests,
                        total: currentTotalTests
                    };

                    // 更新最后的数据
                    lastTotalData = {...data['Total']};
                }
            }

            // 检查数据是否真正发生变化
            const currentSnapshot = JSON.stringify(data);
            const dataChanged = currentSnapshot !== lastProgressSnapshot;

            if (dataChanged) {
                // 只有在数据真正变化时才更新时间戳
                document.getElementById('lastUpdatedTime').textContent = new Date().toLocaleTimeString();
                lastProgressSnapshot = currentSnapshot;
            }

            // 更新数据监控卡片
            updateDashboardCards(data);

            const progressContainer = document.getElementById('progressContainer');

            // 检测是否所有任务都已完成或者这是一个全新的任务
            const isNewTask = Object.keys(previousData).length > 0 &&
                             Object.keys(data).length > 0 &&
                             Object.values(data).some(info => info.completed === 0 &&
                                                    info.total > 0);

            // 检查是否至少有一个活动任务
            const hasActiveTask = Object.values(data).some(info =>
                info.completed < info.total && info.total > 0
            );

            if (isNewTask) {
                // 清空进度容器并重新开始
                progressContainer.innerHTML = '';
                previousData = {};

                // 只有当没有活动任务时才清空日志容器
                if (!hasActiveTask) {
                    const logContainer = document.getElementById('logContainer');
                    logContainer.innerHTML = '';
                    logContainer.appendChild(createEmptyState('等待日志输出...', 'fas fa-terminal'));
                }
            }

            if (Object.keys(data).length !== 0) {
                // 清除空状态
                if (document.getElementById('progressEmptyState')) {
                    progressContainer.innerHTML = '';
                }

                if (!isLogWebSocketInitialized) {
                    initializeLogWebSocket();
                    getConfig();
                    isLogWebSocketInitialized = true;
                }
            } else {
                isLogWebSocketInitialized = false;
            }

            // 移除Total字段，因为我们不需要为它创建进度条，但仍保留它用于统计
            const totalData = data['Total'];
            delete data['Total'];

            // 处理其他工作进程的进度条
            for (const [workerName, progressInfo] of Object.entries(data)) {
                // 检查是否已经存在此worker的进度条
                const existingWorkerProgress = document.querySelector(`.mb-4.p-3.rounded-xl[data-worker='${workerName}']`);

                // 如果有target信息，保存到全局对象中
                if (progressInfo.target && progressInfo.target.trim() !== '') {
                    workerTargets[workerName] = progressInfo.target;
                }

                if (!existingWorkerProgress) {
                    const progress = (progressInfo.completed / progressInfo.total) * 100 || 0; // 防止NaN
                    const workerProgress = document.createElement('div');
                    
                    // 处理target信息，修改默认显示
                    let target = '';
                    if (workerTargets[workerName]) {
                        target = workerTargets[workerName]; // 首选：全局存储的target
                    } else if (progressInfo.target && progressInfo.target.trim() !== '') {
                        target = progressInfo.target; // 次选：当前数据中的target
                    } 
                    
                    // 如果是第一个worker且没有target信息，添加一个默认标签
                    if (target === '' && Object.keys(data).length > 0 && Object.keys(data)[0] === workerName) {
                        target = '主进程';
                        workerTargets[workerName] = target; // 保存以便后续使用
                    }

                    // 根据完成状态设置不同的背景色
                    workerProgress.className = progressInfo.completed === progressInfo.total ?
                        'mb-4 p-3 rounded-xl bg-white/10 backdrop-blur-md shadow-sm border border-white/30 transition-all duration-300 hover:bg-white/20 hover:shadow-md' :
                        'mb-4 p-3 rounded-xl bg-white/10 backdrop-blur-md shadow-sm border border-white/30 transition-all duration-300 hover:bg-white/20 hover:shadow-md';

                    workerProgress.setAttribute('data-worker', workerName);

                    const workerNameElement = document.createElement('div');
                    workerNameElement.className = 'flex items-center mb-2 font-medium';

                    // 确保任务开始后且进度<100%时显示旋转齿轮
                    const iconClass = progressInfo.completed === progressInfo.total ?
                                     'fas fa-check-circle mr-2' :
                                     'fas fa-cog fa-spin mr-2';
                    const iconColor = progressInfo.completed === progressInfo.total ?
                                     'text-status-passed' :
                                     'text-primary-500';

                    // 只有当target不为空时才显示标签
                    const targetSpan = target ? 
                        `<span class="ml-2 px-2 py-1 text-xs rounded-md bg-white/20 backdrop-blur-sm text-gray-700 border border-white/20" data-worker="${workerName}">${target}</span>` : 
                        '';
                        
                    workerNameElement.innerHTML = `<i class="${iconClass} ${iconColor}" data-worker="${workerName}"></i> Worker: ${workerName} ${targetSpan}`;

                    const progressBar = document.createElement('div');
                    progressBar.className = 'h-2 w-full bg-white/20 backdrop-blur-sm rounded-full overflow-hidden mb-1';

                    const progressFill = document.createElement('div');
                    progressFill.className = progressInfo.completed === progressInfo.total ?
                        'h-full bg-status-passed rounded-full transition-all duration-300' :
                        'h-full bg-primary-500 rounded-full transition-all duration-300';
                    progressFill.setAttribute('data-worker', workerName);
                    progressFill.style.width = `${progress}%`;

                    const progressText = document.createElement('div');
                    progressText.className = 'flex justify-between text-xs text-gray-600';
                    progressText.setAttribute('data-worker', workerName);
                    progressText.innerHTML = `<span>${Math.round(progress)}%</span> <span>${progressInfo.completed}/${progressInfo.total}</span>`;

                    progressBar.appendChild(progressFill);
                    workerProgress.appendChild(workerNameElement);
                    workerProgress.appendChild(progressBar);
                    workerProgress.appendChild(progressText);

                    progressContainer.appendChild(workerProgress);
                }
            }

            updateProgressBars(data);
            checkAllProgressCompleted(data);

            // 只有当所有任务都已完成时才重置WebSocket连接
            const allTasksCompleted = Object.values(data).every(info =>
                info.completed === info.total || info.total === 0
            );

            if (allTasksCompleted && isLogWebSocketInitialized) {
                // 不要立即关闭连接，而是标记它可以关闭
                // 这允许最终的日志消息被处理
                setTimeout(() => {
                    if (allTasksCompleted) {
                        isLogWebSocketInitialized = false;
                    }
                }, 2000); // 给日志一些时间完成
            }

            // 保存当前数据用于下次比较
            previousData = {...data};
        };

        document.getElementById('viewReportButton').addEventListener('click', function () {
            window.location.href = '/reports/html/index.html';
        });

        // Initialize event listeners
        function initEventListeners() {
            // Log filters dropdown
            document.getElementById('logFilterButton').addEventListener('click', function() {
                document.getElementById('logFilterDropdown').classList.toggle('hidden');
            });

            // Log filter options
            document.querySelectorAll('.log-filter-option').forEach(option => {
                option.addEventListener('click', function(e) {
                    e.preventDefault();
                    currentFilter = this.getAttribute('data-filter');
                    applyLogFilters();
                    document.getElementById('logFilterDropdown').classList.add('hidden');

                    // Update button text
                    const filterText = this.textContent;
                    document.getElementById('logFilterButton').innerHTML = `<i class="fas fa-filter mr-1"></i> ${filterText} <svg class="-mr-1 ml-1 h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" /></svg>`;

                    showNotification('已应用过滤器', `显示${filterText.toLowerCase()}日志`, 'info');
                });
            });

            // Clear logs button
            document.getElementById('clearLogsButton').addEventListener('click', function() {
                const logContainer = document.getElementById('logContainer');
                logContainer.innerHTML = '';
                logContainer.appendChild(createEmptyState('日志已清除，等待新日志...', 'fas fa-terminal'));
                showNotification('日志已清除', '所有日志条目已被清除', 'info');
            });

            // Download logs button
            document.getElementById('downloadLogsButton').addEventListener('click', function() {
                const logContainer = document.getElementById('logContainer');
                let logText = '';

                document.querySelectorAll('.log-entry').forEach(entry => {
                    logText += entry.textContent + '\n';
                });

                if (logText.trim() === '') {
                    showNotification('没有日志', '没有可导出的日志', 'warning');
                    return;
                }

                const blob = new Blob([logText], { type: 'text/plain' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                const timestamp = new Date().toISOString().replace(/[:.]/g, '-');

                a.href = url;
                a.download = `aomaker-logs-${timestamp}.txt`;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                URL.revokeObjectURL(url);

                showNotification('日志已导出', '日志文件已成功下载', 'success');
            });

            // Log search functionality
            document.getElementById('logSearchInput').addEventListener('input', function() {
                currentLogSearch = this.value.toLowerCase();
                applyLogFilters();
            });

            // Config modal
            document.getElementById('expandConfigButton').addEventListener('click', function() {
                // Copy config table data to modal
                const configTableBodyModal = document.getElementById('configTableBodyModal');
                configTableBodyModal.innerHTML = document.getElementById('configTableBody').innerHTML;
                document.getElementById('configModal').classList.remove('hidden');
            });

            document.getElementById('closeConfigModal').addEventListener('click', function() {
                document.getElementById('configModal').classList.add('hidden');
            });

            // Close notification
            document.getElementById('closeNotification').addEventListener('click', function() {
                hideNotification();
            });
        }

        // Apply log filters based on current filter and search
        function applyLogFilters() {
            const logEntries = document.querySelectorAll('.log-entry');
            const logEmptyState = document.getElementById('logEmptyState');
            let visibleCount = 0;

            logEntries.forEach(entry => {
                const text = entry.textContent.toLowerCase();
                const matchesSearch = currentLogSearch === '' || text.includes(currentLogSearch);
                let matchesFilter = true;

                if (currentFilter === 'error') {
                    matchesFilter = text.includes('error') || text.includes('exception');
                } else if (currentFilter === 'warning') {
                    matchesFilter = text.includes('warning') || text.includes('warn');
                } else if (currentFilter === 'info') {
                    matchesFilter = text.includes('info');
                }

                if (matchesSearch && matchesFilter) {
                    entry.style.display = '';
                    visibleCount++;
                } else {
                    entry.style.display = 'none';
                }
            });

            // Show empty state if no logs are visible
            if (visibleCount === 0 && !logEmptyState) {
                const logContainer = document.getElementById('logContainer');
                const noResultsMsg = currentLogSearch
                    ? `未找到匹配"${currentLogSearch}"的日志`
                    : '没有符合所选过滤器的日志';
                logContainer.appendChild(createEmptyState(noResultsMsg, 'fas fa-search'));
            } else if (logEmptyState && visibleCount > 0) {
                logEmptyState.remove();
            }
        }

        // Show notification
        function showNotification(title, message, type = 'info') {
            const notification = document.getElementById('notification');
            const notificationTitle = document.getElementById('notificationTitle');
            const notificationMessage = document.getElementById('notificationMessage');
            const notificationIcon = document.getElementById('notificationIcon');

            // Set icon based on type
            let iconHtml = '';
            if (type === 'success') {
                iconHtml = '<svg class="h-6 w-6 text-green-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>';
            } else if (type === 'error') {
                iconHtml = '<svg class="h-6 w-6 text-red-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>';
            } else if (type === 'warning') {
                iconHtml = '<svg class="h-6 w-6 text-yellow-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>';
            } else {
                iconHtml = '<svg class="h-6 w-6 text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>';
            }

            notificationIcon.innerHTML = iconHtml;
            notificationTitle.textContent = title;
            notificationMessage.textContent = message;

            // Show notification with animation
            notification.classList.remove('hidden', 'scale-95', 'opacity-0');
            notification.classList.add('scale-100', 'opacity-100');

            // Auto-hide after 5 seconds
            clearTimeout(window.notificationTimeout);
            window.notificationTimeout = setTimeout(hideNotification, 5000);
        }

        // Hide notification
        function hideNotification() {
            const notification = document.getElementById('notification');
            notification.classList.add('scale-95', 'opacity-0');
            setTimeout(() => {
                notification.classList.add('hidden');
            }, 300);
        }

        // 页面加载时的动画效果
        document.addEventListener('DOMContentLoaded', function() {
            // 为所有neu-card添加淡入动画
            document.querySelectorAll('.neu-card').forEach((card, index) => {
                card.classList.add('opacity-0', 'translate-y-5');
                setTimeout(() => {
                    card.classList.add('transition-all', 'duration-500', 'ease-out');
                    card.classList.remove('opacity-0', 'translate-y-5');
                }, 100 + index * 50); // 错开每个卡片的动画时间
            });

            // 初始化所有事件监听器
            initEventListeners();

            // 测试与服务器的连接
            testConnection();
            // 初始化时设置一个默认的"--"显示
            document.getElementById('lastUpdatedTime').textContent = "--";
        });

        // 创建状态空白元素的辅助函数
        function createEmptyState(message, iconClass) {
            const emptyState = document.createElement('div');
            emptyState.id = 'logEmptyState';
            emptyState.className = 'flex flex-col items-center justify-center h-full p-8';

            const icon = document.createElement('i');
            icon.className = iconClass + ' text-5xl text-primary-500 mb-4';

            const text = document.createElement('p');
            text.textContent = message;
            text.className = 'text-gray-600';

            emptyState.appendChild(icon);
            emptyState.appendChild(text);

            return emptyState;
        }

        // 添加更新仪表板卡片的函数
        function updateDashboardCards(data) {
            // 如果存在Total数据，使用它来更新统计指标
            if (data['Total']) {
                const totalInfo = data['Total'];

                // 总测试用例数量
                document.getElementById('totalTestsCount').textContent = totalInfo.total || "-";

                // 已完成的测试用例数量
                document.getElementById('completedTestsCount').textContent = totalInfo.completed || "-";

                // 活动工作者 = 那些进度未完成的工作者
                // 原始计算方式现在可能有问题，直接计算非Total的键的数量
                const activeWorkers = Object.keys(data).filter(name => name !== 'Total').length;
                
                // 确保总是显示正确的数字
                document.getElementById('activeWorkersCount').textContent = activeWorkers > 0 ? activeWorkers : "-";

                // 打印活跃工作者信息便于调试
                console.log("活跃工作者数量:", activeWorkers, "工作者名称:", Object.keys(data).filter(name => name !== 'Total'));

                // 计算整体进度百分比
                if (totalInfo.total > 0) {
                    const overallPercent = Math.round((totalInfo.completed / totalInfo.total) * 100);
                    document.getElementById('overallProgressPercent').textContent = `${overallPercent}%`;
                } else {
                    document.getElementById('overallProgressPercent').textContent = "0%";
                }
            } else {
                // 如果没有Total数据，则显示默认值
                document.getElementById('totalTestsCount').textContent = "-";
                document.getElementById('activeWorkersCount').textContent = "-";
                document.getElementById('completedTestsCount').textContent = "-";
                document.getElementById('overallProgressPercent').textContent = "-";
            }
        }

        // 添加手动测试连接的函数
        function testConnection() {
            fetch(`http://localhost:${port}/status`)
                .then(response => response.json())
                .then(data => {
                    console.log("服务器状态:", data);
                    showNotification("服务器连接正常", "成功连接到服务器", "success");
                })
                .catch(error => {
                    console.error("连接错误:", error);
                    showNotification("服务器连接失败", "无法连接到服务器，请确保服务已启动", "error");
                });
        }

        // Close dropdowns when clicking outside
        document.addEventListener('click', function(event) {
            if (!event.target.closest('#logFilterButton') && !event.target.closest('#logFilterDropdown')) {
                document.getElementById('logFilterDropdown').classList.add('hidden');
            }
        });
    </script>
</body>
</html>
